/**
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_ECMA_VM_H
#define ECMASCRIPT_ECMA_VM_H

#include <random>
#include <tuple>

#include "include/mem/panda_containers.h"
#include "include/mem/panda_string.h"
#include "plugins/ecmascript/runtime/base/config.h"
//#include "plugins/ecmascript/runtime/builtins.h"
#include "plugins/ecmascript/runtime/ecma_call_profiling_table.h"
#include "plugins/ecmascript/runtime/ecma_string_table.h"
#include "plugins/ecmascript/runtime/global_handle_collection.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/js_method.h"
#include "plugins/ecmascript/runtime/interpreter/js_frame.h"
#include "plugins/ecmascript/runtime/js_runtime_options.h"
#include "plugins/ecmascript/runtime/mem/ecma_string.h"
#include "plugins/ecmascript/runtime/mem/object_xray.h"
#include "plugins/ecmascript/runtime/tooling/pt_js_extractor.h"
#include "include/panda_vm.h"
#include "runtime/single_thread_manager.h"
#include "libpandabase/macros.h"
#include "libpandabase/os/library_loader.h"

#include "source_languages.h"

namespace ark {
class JSNApi;
class RuntimeNotificationManager;
namespace panda_file {
class File;
}  // namespace panda_file

namespace ecmascript {
class ConstantPool;
class GlobalEnv;
class ObjectFactory;
class RegExpParserCache;
class EcmaRuntimeStat;
class MemManager;
class JSNativePointer;
class Program;
class JSPromise;
class JSFinalizationRegistry;
enum class PromiseRejectionEvent : uint32_t;

namespace job {
class MicroJobQueue;
}  // namespace job

#ifndef INL_BUILTS
#define INL_BUILTS
enum class BuiltinId : uint16_t { NONE, MATH_SIN };
#endif
namespace builtins {
class RegExpExecResultCache;
}  // namespace builtins

template <typename T>
class JSHandle;
class JSArrayBuffer;
class JSFunction;
class Program;
class ModuleManager;
class EcmaModule;
using HostPromiseRejectionTracker = void (*)(const EcmaVM *vm, const JSHandle<JSPromise> promise,
                                             const JSHandle<JSTaggedValue> reason,
                                             const PromiseRejectionEvent operation, void *data);
using PromiseRejectCallback = void (*)(void *info);

class EcmaVM final : public PandaVM {
    using PtJSExtractor = tooling::ecmascript::PtJSExtractor;

public:
    static EcmaVM *Cast(PandaVM *object)
    {
        return reinterpret_cast<EcmaVM *>(object);
    }

    PANDA_PUBLIC_API static EcmaVM *Create(const JSRuntimeOptions &options);

    PANDA_PUBLIC_API static bool Destroy(PandaVM *vm);

    explicit EcmaVM(JSRuntimeOptions options);

    static Expected<EcmaVM *, PandaString> Create(Runtime *runtime, const JSRuntimeOptions &options);

    EcmaVM();

    ~EcmaVM() override;

    bool ExecuteFromPf(std::string_view filename, std::string_view entryPoint, const std::vector<std::string> &args,
                       bool isModule = false);

    bool ExecuteFromBuffer(const void *buffer, size_t size, std::string_view entryPoint,
                           const std::vector<std::string> &args);

    Expected<JSTaggedValue, Runtime::Error> GetInvocableFunction(const panda_file::File &pf,
                                                                 const PandaString &methodName);

    PtJSExtractor *GetDebugInfoExtractor(const panda_file::File *file);

    bool IsInitialized() const
    {
        return vmInitialized_;
    }

    void HandleLdaStr(Frame *frame, BytecodeId stringId) override;

    ObjectFactory *GetFactory() const
    {
        return factory_;
    }

    void SaveProfileInfo() override;

    bool Initialize() override;

    bool InitializeFinish() override;
    void UninitializeThreads() override;
    void PreStartup() override {}
    void PreZygoteFork() override {}
    void PostZygoteFork() override {}
    void InitializeGC() override
    {
        mm_->InitializeGC(this);
    }
    PANDA_PUBLIC_API void StartGC() override
    {
        mm_->StartGC();
    }
    void StopGC() override
    {
        mm_->StopGC();
    }

    void VisitVmRoots(const GCRootVisitor &visitor) override;
    void UpdateVmRefs(const GCRootUpdater &gcRootUpdater) override;

    PandaVMType GetPandaVMType() const override
    {
        return PandaVMType::ECMA_VM;
    }

    // Remove global handler for JSFunction corresponding to this method
    void CleanUpTask(Method *method) override;

    LanguageContext GetLanguageContext() const override
    {
        return Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ECMASCRIPT);
    }

    ark::mem::HeapManager *GetHeapManager() const override
    {
        return mm_->GetHeapManager();
    }

    ark::mem::GC *GetGC() const override
    {
        return mm_->GetGC();
    }

    ark::mem::GCTrigger *GetGCTrigger() const override
    {
        return mm_->GetGCTrigger();
    }

    StringTable *GetStringTable() const override
    {
        UNREACHABLE();
    }

    ark::mem::GCStats *GetGCStats() const override
    {
        return mm_->GetGCStats();
    }

    ark::mem::MemStatsType *GetMemStats() const override
    {
        return mm_->GetMemStats();
    }

    ark::mem::GlobalObjectStorage *GetGlobalObjectStorage() const override
    {
        return mm_->GetGlobalObjectStorage();
    }

    coretypes::String *ResolveString(const panda_file::File &pf, panda_file::File::EntityId id) override
    {
        ASSERT(stringTable_ != nullptr);
        auto str = stringTable_->ResolveString(pf, id);
        return coretypes::String::Cast(str);
    }

    coretypes::String *ResolveString(ConstantPool *cp, panda_file::File::EntityId id);

    coretypes::String *ResolveString(Frame *frame, panda_file::File::EntityId id) override;

    void HandleReturnFrame() override;

    MonitorPool *GetMonitorPool() const override
    {
        UNREACHABLE();
    }

    SingleThreadManager *GetThreadManager() const override
    {
        return threadManager_;
    }

    void VisitStringTable(const StringTable::StringVisitor &visitor, mem::VisitGCRootFlags flags) override
    {
        GetEcmaStringTable()->VisitRoots(visitor, flags);
    }

    void VisitStrings(const StringTable::StringVisitor &visitor) override
    {
        VisitStringTable(visitor, mem::VisitGCRootFlags::ACCESS_ROOT_ALL);
    }

    void SweepVmRefs(const GCObjectVisitor &gcObjectVisitor) override;

    bool UpdateMovedStrings(const GCRootUpdater &gcRootUpdater) override
    {
        return GetEcmaStringTable()->UpdateMoved(gcRootUpdater);
    }

    ManagedThread *GetAssociatedThread() const override
    {
        return thread_;
    }

    JSThread *GetAssociatedJSThread() const
    {
        return thread_;
    }

    CompilerInterface *GetCompiler() const override
    {
        return compiler_;
    }

    Rendezvous *GetRendezvous() const override
    {
        return rendezvous_;
    }

    compiler::RuntimeInterface *GetCompilerRuntimeInterface() const override
    {
        return runtimeIface_;
    }

    ObjectHeader *GetOOMErrorObject() override
    {
        // preallocated OOM is not implemented for JS
        UNREACHABLE();
    }

    ark::mem::ReferenceProcessor *GetReferenceProcessor() const override
    {
        return ecmaReferenceProcessor_.get();
    }

    const RuntimeOptions &GetOptions() const override
    {
        return Runtime::GetOptions();
    }

    static const JSRuntimeOptions &GetJSOptions()
    {
        return options_;
    }

    PANDA_PUBLIC_API JSHandle<GlobalEnv> GetGlobalEnv() const;

    JSHandle<job::MicroJobQueue> GetMicroJobQueue() const;

    bool ExecutePromisePendingJob() const;

    RegExpParserCache *GetRegExpParserCache() const
    {
        ASSERT(regExpParserCache_ != nullptr);
        return regExpParserCache_;
    }

    PANDA_PUBLIC_API JSMethod *GetMethodForNativeFunction(const void *func);

    EcmaStringTable *GetEcmaStringTable() const
    {
        ASSERT(stringTable_ != nullptr);
        return stringTable_;
    }

    bool HasEcmaCallProfileTable() const
    {
        return callProfilingTable_ != nullptr;
    }

    EcmaCallProfilingTable *GetEcmaCallProfileTable() const
    {
        ASSERT(callProfilingTable_ != nullptr);
        return callProfilingTable_;
    }

    JSThread *GetJSThread() const
    {
        return thread_;
    }

    bool ICEnable() const
    {
        return icEnable_;
    }

    void HandleReferences([[maybe_unused]] const GCTask &task, const mem::GC::ReferenceClearPredicateT &pred) override
    {
        LOG(DEBUG, REF_PROC) << "Start processing cleared references";
        mem::GC *gc = mm_->GetGC();
        gc->ProcessReferences(gc->GetGCPhase(), task, pred);
    }

    void PushToArrayDataList(JSNativePointer *array);
    void RemoveArrayDataList(JSNativePointer *array);

    JSHandle<ecmascript::JSTaggedValue> GetEcmaUncaughtException() const;
    void EnableUserUncaughtErrorHandler();

    template <typename Callback>
    void EnumeratePandaFiles(Callback cb) const
    {
        os::memory::LockHolder lock(pandaFileWithProgramLock_);
        for (const auto &iter : pandaFileWithProgram_) {
            if (!cb(std::get<0>(iter), std::get<1>(iter))) {
                break;
            }
        }
    }

    template <typename Callback>
    void EnumerateProgram(Callback cb, const std::string &pandaFile) const
    {
        os::memory::LockHolder lock(pandaFileWithProgramLock_);
        for (const auto &iter : pandaFileWithProgram_) {
            if (pandaFile == std::get<1>(iter)->GetFilename()) {
                cb(std::get<0>(iter));
                break;
            }
        }
    }

    EcmaRuntimeStat *GetRuntimeStat() const
    {
        return runtimeStat_;
    }

    void SetRuntimeStatEnable(bool flag);

    bool IsRuntimeStatEnabled() const
    {
        return runtimeStatEnabled_;
    }

    bool IsOptionalLogEnabled() const
    {
        return optionalLogEnabled_;
    }

    /// @brief Calls GC::RegisterNativeAllocation and allocates memory in internal space
    [[nodiscard]] void *AllocAndRegisterNative(size_t size);

    /// @brief Calls GC::RegisterNativeFree and frees memory in internal space
    void FreeAndRegisterNative(void *mem, size_t size);

    void Iterate(const RootVisitor &v);

    void CollectGarbage() const;

    void ProcessReferences(const WeakRootVisitor &v0);
    void HandleGCRoutineInMutator() override;

    JSHandle<JSTaggedValue> GetModuleByName(JSHandle<JSTaggedValue> moduleName);

    void ExecuteModule(std::string_view moduleFile, std::string_view entryPoint, const std::vector<std::string> &args);

    ModuleManager *GetModuleManager() const
    {
        return moduleManager_;
    }

    void SetupRegExpResultCache();

    JSHandle<JSTaggedValue> GetRegExpCache() const
    {
        return JSHandle<JSTaggedValue>(reinterpret_cast<uintptr_t>(&regexpCache_));
    }

    void SetRegExpCache(JSTaggedValue newCache)
    {
        regexpCache_ = newCache;
    }

    RuntimeNotificationManager *GetNotificationManager() const
    {
        return notificationManager_;
    }

    bool IsBytecodeProfilingEnabled() const override
    {
        return isProfilingEnabled_;
    }

    std::unique_ptr<const panda_file::File> OpenPandaFile(std::string_view location) override;

    coretypes::String *GetNonMovableString(const panda_file::File &pf, panda_file::File::EntityId id) const override;

    const PandaVector<JSMethod *> &GetNativeMethods() const
    {
        return nativeMethods_;
    }

    void SetData(void *data)
    {
        data_ = data;
    }

    void SetPromiseRejectCallback(PromiseRejectCallback cb)
    {
        promiseRejectCallback_ = cb;
    }

    PromiseRejectCallback GetPromiseRejectCallback() const
    {
        return promiseRejectCallback_;
    }

    void SetHostPromiseRejectionTracker(HostPromiseRejectionTracker cb)
    {
        hostPromiseRejectionTracker_ = cb;
    }

    void PromiseRejectionTracker(const JSHandle<JSPromise> &promise, const JSHandle<JSTaggedValue> &reason,
                                 const PromiseRejectionEvent operation)
    {
        if (hostPromiseRejectionTracker_ != nullptr) {
            hostPromiseRejectionTracker_(this, promise, reason, operation, data_);
        }
    }

    void RegisterFinalizationRegistry(JSHandle<JSFinalizationRegistry> registry);

    void AddMethodToProfile(JSMethod *method)
    {
        profilesMethods_.insert(method);
    }

    void DumpHeap(PandaOStringStream *oStr) const final;

    PandaString GetClassesFootprint() const final;

    static constexpr size_t GetGlobalEnvOffset()
    {
        return MEMBER_OFFSET(EcmaVM, globalEnv_);
    }

    std::default_random_engine &GetRandomEngine()
    {
        ASSERT(randomEngine_);
        return *randomEngine_;
    }

protected:
    bool CheckEntrypointSignature([[maybe_unused]] Method *entrypoint) override
    {
        return true;
    }

    Expected<int, Runtime::Error> InvokeEntrypointImpl(Method *entrypoint,
                                                       const std::vector<std::string> &args) override;

    void HandleUncaughtException() override;

    void PrintJSErrorInfo(const JSHandle<JSTaggedValue> &exceptionInfo);

private:
    static constexpr uint32_t THREAD_SLEEP_TIME = 16 * 1000;
    static constexpr uint32_t THREAD_SLEEP_COUNT = 100;

    void AddPandaFile(const panda_file::File *pf, bool isModule);
    void SetProgram(Program *program, const panda_file::File *pf);
    bool IsFrameworkPandaFile(std::string_view filename) const;

    void SetGlobalEnv(GlobalEnv *global);

    void SetMicroJobQueue(job::MicroJobQueue *queue);

    bool Execute(std::unique_ptr<const panda_file::File> pf, std::string_view entryPoint,
                 const std::vector<std::string> &args, bool isModule = false);

    Expected<int, Runtime::Error> InvokeEcmaEntrypoint(const panda_file::File &pf, const PandaString &methodName,
                                                       const std::vector<std::string> &args);

    void InitializeEcmaScriptRunStat();

    void RedirectMethod(const panda_file::File &pf);

    bool VerifyFilePath(const PandaString &filePath) const;

    void ClearBufferData();

    void ClearNativeMethodsData();

    Method *GetNativeMethodWrapper();
    void LoadEcmaStdLib();
    const panda_file::File *GetLastLoadedPandaFile();

    void InitializeRandomEngine()
    {
        ASSERT(!randomEngine_);
        std::random_device rd;
        randomEngine_.emplace(rd());
    }

    NO_MOVE_SEMANTIC(EcmaVM);
    NO_COPY_SEMANTIC(EcmaVM);

    mem::MemoryManager *mm_ {nullptr};
    PandaUniquePtr<ark::mem::ReferenceProcessor> ecmaReferenceProcessor_;

    SingleThreadManager *threadManager_ {nullptr};
    Rendezvous *rendezvous_ {nullptr};
    bool isTestMode_ {false};

    PandaSet<JSMethod *> profilesMethods_;

    // VM startup states.
    static JSRuntimeOptions options_;
    bool icEnable_ {true};
    bool vmInitialized_ {false};
    bool isUncaughtExceptionRegistered_ {false};
    bool isProfilingEnabled_ {false};

    // VM memory management.
    EcmaCallProfilingTable *callProfilingTable_ {nullptr};
    EcmaStringTable *stringTable_ {nullptr};
    ObjectFactory *factory_ {nullptr};
    PandaVector<JSNativePointer *> arrayBufferDataList_;

    // VM execution states.
    JSThread *thread_ {nullptr};
    RegExpParserCache *regExpParserCache_ {nullptr};
    JSTaggedValue globalEnv_ {JSTaggedValue::Hole()};
    JSTaggedValue regexpCache_ {JSTaggedValue::Hole()};
    JSTaggedValue microJobQueue_ {JSTaggedValue::Hole()};
    bool runtimeStatEnabled_ {false};
    EcmaRuntimeStat *runtimeStat_ {nullptr};

    // App framework resources.
    JSTaggedValue frameworkProgram_ {JSTaggedValue::Hole()};
    PandaString frameworkAbcFileName_;
    const panda_file::File *frameworkPandaFile_ {nullptr};
    PandaVector<JSMethod *> frameworkProgramMethods_;

    // VM resources.
    PandaVector<JSMethod *> nativeMethods_ {};
    ModuleManager *moduleManager_ {nullptr};
    bool optionalLogEnabled_ {false};
    // weak reference need Redirect address
    PandaVector<std::tuple<Program *, const panda_file::File *, bool>> pandaFileWithProgram_
        GUARDED_BY(pandaFileWithProgramLock_);
    mutable os::memory::Mutex pandaFileWithProgramLock_;
    PandaMap<ark::PandaString, PandaMap<size_t, ark::PandaMap<ark::compiler::AnyBaseType, size_t>>>
        functionsArgTypeCache_;
    Method *nativeMethodWrapper_ {nullptr};
    CompilerInterface *compiler_ {nullptr};
    compiler::RuntimeInterface *runtimeIface_ {nullptr};

    // Debugger
    RuntimeNotificationManager *notificationManager_ {nullptr};
    PandaUnorderedMap<const panda_file::File *, std::unique_ptr<PtJSExtractor>> extractorCache_;

    // Registered Callbacks
    PromiseRejectCallback promiseRejectCallback_ {nullptr};
    HostPromiseRejectionTracker hostPromiseRejectionTracker_ {nullptr};
    void *data_ {nullptr};
    PandaList<JSFinalizationRegistry *> finalizationRegistries_;
    // optional for lazy initialization
    std::optional<std::default_random_engine> randomEngine_;

    friend class ObjectFactory;
    friend class ark::JSNApi;
    friend class EvalUtils;
};
}  // namespace ecmascript
}  // namespace ark

#endif
